package image;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**A swing component which allows convenient editing of integers.*/
public class NumberEditor extends JPanel implements ChangeListener,ActionListener, PropertyChangeListener
	{
	private static final long serialVersionUID = -7710463008559910401L;

	private static final int SLIDER_MAX = 65536;
	
	private int minimum,maximum,value;
	private IntegerField field;
	private RepeatButton upButton,downButton;
	private JSlider slider;
	private boolean discardEvents;
	private PropertyEventSupport pcs;
	private JLabel label;
	private double sliderValuePower;
	/**
	 * 
	 * @param min Minimum permitted value. May be null for none.
	 * @param max Maximum permitted value. May be null for none.
	 * @param currentValue The initial value.
	 * @param title The string displayed over the NumberEditor.
	 * @param sliderValuePower When moving the slider, the resulting value is the slider value, in the range 0-1, to the xth power, 
	 * where x is sliderValuePower. If 1, the behaviour is linear. If higher than 1, a larger part of the slider will be reserved
	 * for the lower values, close to the minimum and vica versa. Cannot be smaller or equal to 0.  
	 */
	public NumberEditor(int min, int max, int currentValue, String title, double sliderValuePower)
		{
		super();
		if(pcs==null)
			pcs = new PropertyEventSupport(this);
		
		this.sliderValuePower = sliderValuePower;
		label = new JLabel(title);
		label.setFont(Fonts.smallFont);
		
		discardEvents=false;
		
		
		value=currentValue;
		minimum=min;
		maximum=max;
		
		upButton = new RepeatButton(IconLib.get("btnUp"));
		upButton.addActionListener(this);
		upButton.setBorder(BorderFactory.createRaisedBevelBorder());
		downButton = new RepeatButton(IconLib.get("btnDown"));
		downButton.addActionListener(this);
		downButton.setBorder(BorderFactory.createRaisedBevelBorder());
		
		slider = new JSlider(0,SLIDER_MAX,sliderValueOf(value));
		//if(UIManager.getLookAndFeel() instanceof javax.swing.plaf.metal.MetalLookAndFeel)
		slider.setUI(new ThumbSliderUI(slider));
	
		slider.addChangeListener(this);
		slider.setPreferredSize(new Dimension(36,16));
		field=new IntegerField(minimum,maximum,value,
				(UIManager.getLookAndFeel().getClass().getName().equals(
						"org.pushingpixels.substance.api.skin.SubstanceGraphiteLookAndFeel"))?3:4);
		field.addValuePropertyChangeListener(this);
		setLayout(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		
		c.gridwidth = 3;
		add(label,c);
		c.gridwidth = 1;
		c.gridy=1;
		add(upButton,c);
		c.gridy=2;
		add(downButton,c);
		
		c.gridx=1;
		c.gridy=1;
		c.gridheight = 2; 
		add(field,c);
		
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx=1;
		c.gridx=2;
		add(slider,c);
		}

	private int valueOfSlider(int val)
		{
		return (int) (minimum+Math.round(((maximum-minimum)*Math.pow((double)val/SLIDER_MAX, sliderValuePower))));
		}
	private int sliderValueOf(int val)
		{
		return (int) Math.round((SLIDER_MAX*Math.pow((double)(val-minimum)/(maximum-minimum), 1d/sliderValuePower)));
		}

	private void valueChanged(int newInputValue,Object src)
		{
		int newIntValue;
		if(src == slider)
			newIntValue = valueOfSlider(newInputValue);
		else
			newIntValue = newInputValue;
		if (newIntValue<minimum) newIntValue=minimum;
		if (newIntValue>maximum) newIntValue=maximum;
		
		if((newIntValue!=value || src==slider) && !discardEvents)
			{
			discardEvents=true;
			if(src!=field)
				field.setValue(newIntValue,true,true);
			slider.setValue(sliderValueOf(newIntValue));
			pcs.firePropertyChange("VALUE", value, newIntValue);
			value=newIntValue;
			discardEvents=false;
			}
		}
	public void setValue(int newValue, boolean fireEvents)
		{
		discardEvents=true;
		field.setValue(newValue,true,true);
		slider.setValue(sliderValueOf(newValue));
		if(fireEvents)
			pcs.firePropertyChange("VALUE", value, newValue);
		value=newValue;
		discardEvents=false;
		}

	public boolean getValueIsAdjusting()
		{
		return slider.getValueIsAdjusting() || upButton.getValueIsAdjusting() || downButton.getValueIsAdjusting();
		}

	@Override
	public void actionPerformed(ActionEvent e)
		{
		Object o = e.getSource();
		if (o == upButton)
			{
			valueChanged(value+1,e.getSource());
			}
		else if (o == downButton)
			{
			valueChanged(value-1,e.getSource());
			}
		}
	public int getValue()
		{
		return value;
		}

	@Override
	public void stateChanged(ChangeEvent e)
		{
		valueChanged(slider.getValue(),e.getSource());
		}
	/**Listen to events for when the value is changing.*/
	public void addPropertyChangeListener(PropertyChangeListener obj)
		{
		if(pcs==null)
			pcs = new PropertyEventSupport(this);
		pcs.addPropertyChangeListener(obj);
		}

	@Override
	public void propertyChange(PropertyChangeEvent e)
		{
		valueChanged((Integer) e.getNewValue(),e.getSource());
		}
	@Override
	public void setEnabled(boolean en)
		{
		super.setEnabled(en);
		field.setEnabled(en); 
		upButton.setEnabled(en); 
		downButton.setEnabled(en); 
		label.setEnabled(en); 
		slider.setEnabled(en);
		}
	@Override
	public void setToolTipText(String tip)
		{
		super.setToolTipText(tip);
		field.setToolTipText(tip); 
		upButton.setToolTipText(tip); 
		downButton.setToolTipText(tip); 
		label.setToolTipText(tip); 
		slider.setToolTipText(tip);
		}
	}
